<?php
/*
 * Flickr Sets by Tag Class (FST)
 * version 0.291
 * Abe Yang <abeyang@cal.berkeley.edu> (c) 2008
 * http://code.google.com/p/flickrsetsbytag/
 *
 * FST is freely distributable under the terms of an MIT-style license.
 * Please visit http://code.google.com/p/flickrsetsbytag/ for more details.
 *
/* ---------------------------------------------------------------------------------- */

// include files
require_once('phpFlickr.php');
require_once('Lite.php');
require_once('fst.info.php');

define('FST_VERSION'	, '0.291');
define('FST_PATH'		, dirname(__FILE__) . '/');
define('FST_LIFETIME'	, '9999999999');
define('FST_GROUPNAME'	, 'fst');
define('FST_MAXTIME'	, '250');
define('FST_KEYWORD'	, 'allalbumslist');
define('FST_CACHE_DIR'	, FST_PATH . 'cache/');
define('FST_USER_GROUP'	, 'gracepoint');
define('LOG_FILE'		, FST_PATH . 'log.txt');

class FST {
	
	var $f;
	var $time_start;
	var $time_elapsed;
	var $allalbums;
	var $gallery = '';
	
	// user options
	var $cache_dir;
	var $is_cron = false;		// must be set manually ($fst->is_cron = true)
	
	function FST($cache_dir = FST_CACHE_DIR) {
		$this->clearTimer();
		$this->cache_dir = $cache_dir;
		
		// create new phpFlickr object
		$this->f = new phpFlickr(FLICKR_KEY, FLICKR_SECRET);
		$this->f->setToken(FLICKR_TOKEN);
		$this->f->auth('write');
		
		if (FST_STANDALONE) $this->gallery = 'index.php';
	}
	
	/* render html (view) */
	
	function showAlbum($albumid, $matchtag = FST_DEFAULT_TAG, $thumbsize = 't', $info = 'showAlbum') {
		$matchtag = checkTag($matchtag);
		$album = $this->getAlbum($albumid, $matchtag, $info);
		
		$str = $this->breadCrumbs($matchtag, $album['title']);
		$str .= '<h2>' . $album['title'] . '</h2>' . "\n";
		$str .= '<div>' . $album['description'] . '</div>' . "\n";
		foreach($album['photo'] as $photo) {
			$str .= $this->linkToPhoto($photo['id'], $albumid, $matchtag, '', $this->photo($photo, $thumbsize));
		}
		return $str;
	}
	
	function showAlbumsList($matchtag = FST_DEFAULT_TAG, $thumbsize = 't', $limit = 0, $length = 0, $info = 'showAlbumsList') {
		$matchtag = checkTag($matchtag);
		$albums = $this->getAlbumsListInTag($matchtag, $info);
		$i = 0;
		
		$str = '<div class="fst-tag-' . $matchtag . '">';
		foreach($albums as $album) {
			$i++;
			$photo = array(
				'id' 		=> $album['primary'],
				'server' 	=> $album['server'],
				'secret' 	=> $album['secret'],
				'title' 	=> $album['description'],
				'farm'		=> $album['farm']
			);
			
			$str .= '<div class="fst-album">'. "\n";
			$str .= $this->linkToAlbum($album['id'], $matchtag, $this->photo($photo, $thumbsize));
			$str .= '<div class="fst-title">' . $this->linkToAlbum($album['id'], $matchtag,  $album['title']) . '</div>'. "\n"; 
			$str .= '<div class="fst-desc">' . $this->trim($album['description'], $length) . '</div></div>' . "\n";
			if ($i == $limit) break;
		}
		$str .= '</div>';
		return $str;
	}
	
	// note the order of $limit & $thumbsize is switched
	function showRecentAlbums($matchtag = FST_DEFAULT_TAG, $limit = 5, $length = 0, $thumbsize = 's', $info = 'showRecentAlbums') {
		$matchtag = checkTag($matchtag);
		$info .= "( $limit )";
		
		$str = '<div class="fst-recent">' . "\n";
		$str .= $this->showAlbumsList($matchtag, $thumbsize, $limit, $length, $info);
		$str .= '</div>' . "\n";
		return $str;
	}
	
	function albumDate($datestring, $album) {
		return date($datestring, strtotime($album['datetaken']));
	}
	
	/* helper render functions: photos */
	
	// construct photo image
	function photo($photo, $size = '') {
		$imgurl = $this->photoUrl($photo, $size);
		$linkurl = $this->photoUrl($photo);
		return '<img alt="' . $photo['title'] . '" src="' . $imgurl . '" />';
	}
	
	function photoUrl($photo, $size = '') {
		// error check
		if (!($photo['server'] && $photo['id'] && $photo['secret'] )) {
			$this->log('ERROR', 'constructPhoto', 'missing server, id, or secret params in $photo');
			return;
		}
		
		// for some unknown reason, some farm id's are not getting picked up...
		$farm = ($photo['farm']) ? $photo['farm'] : 1;
		
		if ($size) $size = '_' . $size;		
		if ($size == '_o') {
			$url = 'http://farm' . $farm . '.static.flickr.com/' . $photo['server'] . '/' . $this->photoUniqueId($photo, true) . '_o' . '.' . $photo['originalformat'];
		}
		else {
			$url = 'http://farm' . $farm . '.static.flickr.com/' . $photo['server'] . '/' . $this->photoUniqueId($photo) . $size . '.jpg';			
		}
		
		return $url;
	}
	
	function photoUniqueId($photo, $isoriginal = false) {
		$secret = ($isoriginal) ? $photo['originalsecret'] : $photo['secret'];
		return $photo['id'] . '_' . $secret;
	}
	
	/* helper render functions: links */
	
	// url is slightly different b/n friendly/non-friendly
	function linkToAlbumsList($matchtag = FST_DEFAULT_TAG, $content, $params = '') {
		$prefix = (FST_FRIENDLY_URL) ? FST_ROOT_DIR : $this->gallery;
		return $this->link($prefix . '#tag-' . $matchtag, $content, $params);
	}
	
	function linkToAlbum($albumid, $matchtag = FST_DEFAULT_TAG, $content, $params = '') {
		if (FST_FRIENDLY_URL) 
			$o = $this->link(FST_ROOT_DIR . $matchtag . '/album/' . $albumid, $content, $params);
		else $o = $this->link($this->gallery . '?tag=' . $matchtag . '&album=' . $albumid, $content, $params);
		return $o;
	}

	// params: eg: id="buttonleft" class="button"
	function link($href, $content, $params = '') {
		if ($params) $params .= ' ';
		return '<a ' . $params . 'href=" ' . $href . '">' . $content . '</a>' . "\n";
	}
	
	/* photos in an album */
	
	function getAlbum($albumid, $matchtag = FST_DEFAULT_TAG, $info = 'getAlbum') {
		$matchtag = checkTag($matchtag);
		$key = 'album' . $albumid;
		$album = $this->load($key);
		// album doesn't exist in cache
		if (!$album) {
			// initialize vars
			$doesexist = false;
			$i = 0;
			// check if album exists within allalbumslist
			$this->getAllAlbumsList();
			foreach ($this->allalbums as $a) {
				if ($a['id'] == $albumid) {
					$doesexist = true;
					// update album itself
					$album = $this->updateAlbum($albumid, $info);
					// update album in all-list
					$this->allalbums[$i] = $album;
					unset($this->allalbums[$i]['photo']);
					$this->save($this->allalbums, FST_KEYWORD, $info);
					break;
				}
				$i++;
			}
			
			// album id does not exist in all-list. throw an error
			// TODO: ideally, this should now check flickr to see if album exists...
			if (!$doesexist) {
				$desc = "album $albumid does not exist!";
				$this->log('ERROR', $info, $desc);
				echo $desc;
				return;
			}
		}	// endif !$album
		
		// filter through photos: look for match in tag
		$photos = $this->subsetBasedOnTag($album['photo'], $matchtag);
		$album['photo'] = $photos;
		
		return $album;
	}
	
	// this should NOT be used to create albums outside of all-list, but only for those that already exist in all-list
	// (thus, there should be an outside mechanism that updates all-list)
	function updateAlbum($albumid, $info = 'updateAlbum') {
		$this->startTimer();

		$albuminfo = $this->f->photosets_getInfo($albumid);
		$album = $this->f->photosets_getPhotos($albumid);
		// error check
		if (!$album) {
			$desc = "cannot find album $albumid in Flickr";
			$this->log('ERROR', $info, $desc);
			echo $desc;
			return false;
		}
		// TODO: should check ownername...
		// else if ($album['ownername'] != $ownername) echo 'wrong owner: '. $album['ownername'];
		
		$photos = array();
		foreach($album['photo'] as $photo) {
			$photoinfo = $this->f->photos_getInfo($photo['id']);
			$photo['originalsecret'] = $photoinfo['originalsecret'];
			$photo['originalformat'] = $photoinfo['originalformat'];
			$photo['tags'] = $this->serializeTags($photoinfo);
			$photo['description'] = $photoinfo['description'];
			$photo['datetaken'] = $photoinfo['dates']['taken'];

			$sizes = $this->f->photos_getSizes($photo['id']);
			$photo['sizes'] = array(
				't' => array($sizes[1]['width'], $sizes[1]['height']),
				's' => array($sizes[2]['width'], $sizes[2]['height']),
				'm' => array($sizes[3]['width'], $sizes[3]['height']),
				'l' => array($sizes[4]['width'], $sizes[4]['height']),
				'o' => array($sizes[5]['width'], $sizes[5]['height'])
			);
			
			// extra info to add to album if photo is primary
			if ($photo['isprimary']) {
				$album['secret'] = $photo['secret'];
				$album['server'] = $photo['server'];
				$album['farm'] = $photo['farm'];
				$album['tags'] = $photo['tags'];
				$album['datetaken'] = $photo['datetaken'];
			}
			
			// visibility
			$visstr = '';
			$vis = $photoinfo['visibility'];
			if ($vis['ispublic']) $visstr .= 'ispublic ';
			if ($vis['isfriend']) $visstr .= 'isfriend ';
			if ($vis['isfamily']) $visstr .= 'isfamily ';
			$photo['visibility'] = $visstr;
			
			$photos[] = $photo;
		}
		
		// update $album
		$album['photo'] = $photos;
		$album['title'] = $albuminfo['title'];
		$album['description'] = $albuminfo['description'];
		$album['lastupdate'] = today();
		unset($album['page'], $album['per_page'], $album['pages'], $album['owner'], $album['ownername']);

		$key = 'album' . $albumid;
		$desc = sizeof($album['photo']) . ' photos';
		$this->logUrl();
		return $this->save($album, $key, $info, $desc);
	}
	
	/* albums list in a given tag */
	
	function getAlbumsListInTag($matchtag = FST_DEFAULT_TAG, $info = 'getAlbumsListInTag') {
		$matchtag = checkTag($matchtag);
		$this->getAllAlbumsList($info);		// set $this->allalbums
		$somealbums = $this->subsetBasedOnTag($this->allalbums, $matchtag);
		
		return $somealbums;
	}
	
	/* all albums list */
	
	function getAllAlbumsList($info = 'getAllAlbumsList') {
		if (!$this->allalbums) {
			$this->allalbums = $this->load(FST_KEYWORD);
			if (!$this->allalbums) $this->updateAllAlbumsList($info);
		}
	}
	
	function updateAllAlbumsList($info = 'updateAllAlbumsList') {
		$this->startTimer();
		// fetch from flickr first
		$data = $this->f->photosets_getList();
		$albums = array();

		// fetch tags for each album's primary photo
		// NOTE: is this a "safe" way of retrieving album tags? won't some photos be skipped?
		foreach($data['photoset'] as $album) {
			$photoinfo = $this->f->photos_getInfo($album['primary']);
			$tagstring = $this->serializeTags($photoinfo);
			
			// update (add/remove) extra info for album
			$album['total'] = $album['photos'];
			$album['tags'] = $tagstring;
			$album['lastupdate'] = today();
			$album['datetaken'] = $photoinfo['dates']['taken'];
			unset($album['photos']);
			$albums[] = $album;
		}	// end foreach (albums)
		
		$desc = sizeof($albums) . ' albums';
		$this->logUrl();
		$this->allalbums = $this->save($albums, FST_KEYWORD, $info, $desc);
	}
	
	/* Admin Functions */
	
	// display admin console
	function showAdminConsole($album = '', $tag = '', $type = '', $linktoAdmin = false) {
		$html = '<div id="admin-console">';

		if ($type) $html .= '<em>' . $type . ' Successful</em>';

		$html .='<form action="../webgallery/index.php" method="get" accept-charset="utf-8">' .
				'View ' . $this->showAllAlbumsListWidget($album) .
				' that contains <input type="text" name="tag" value="' . $tag . '" id="tag"> tag.' .

				'<input type="hidden" name="debug" value="1" id="debug">' .
				'<input type="submit" value="Go &rarr; &nbsp;">' .
			'</form>';
		
		if ($linktoAdmin) $html .= '<div class="small">Current Flickr ID: ' . $album . '. Return to <a href="../admin/">Admin Jump Page</a></div>';

		$html .= '</div> <!-- end #admin-console -->';
		echo $html;
	}	// end showAdminConsole()
	
	// show drop down menu of all albums 
	function showAllAlbumsListWidget($selected_album = '') {
		$this->getAllAlbumsList();	// $this->allalbums is now set
		$widget = '<select name="album" id="album">' . "\n" .
			'<option value=""></option>' . "\n";


		foreach($this->allalbums as $album) {
			if ($album['id']) {
				$s = ($selected_album == $album['id']) ? ' selected="selected"' : '';
				$date = $this->albumDate('m/d/y', $album) . ' ';
				$widget .= '<option value="' . $album['id'] . '"' . $s . '>' . $album['title'] . ' ('. $album['total'] . ') - ' . $date . '</option>' . "\n";
			}
		}	// end foreach
		$widget .= '</select>';
		return $widget;
	}	// end showAllAlbumsListWidget()
	
	/* helper FST functions */
	
	// given an array of some sort, return a subset based on a tag
	function subsetBasedOnTag($array, $matchtag = FST_DEFAULT_TAG) {
		$matchtag = checkTag($matchtag);
		// show tag, else show everything
		if ($matchtag != 'all') {
			$objects = array();		// create empty array
			foreach ($array as $object) {
				// must have a 'space' after $matchtag:
				// this differentiates b/n, say, 'kairos' and 'kairos2'
				if (isTagInString($matchtag, $object['tags'])) {
					$objects[] = $object;
				}
			}	// end foreach (albums)
			return $objects;
		}
		else return $array;
	}
	
	// given a 'photoinfo' array, serialize them into a string
	function serializeTags($photoinfoarray) {
		$tagstring = '';
		$tags = $photoinfoarray['tags']['tag'];
		foreach ($tags as $tag) {
			$tagstring .= $tag['_content'] . ' ';
		}	// end foreach (tags)
		return $tagstring;
	}
	
	function log($key='log', $info='', $description='') {
		if ($description) $info .= ': '. $description;
		if ($this->is_cron) $info = 'CRON: ' . $info;
		error_log(today() . " - $key - $info\n", 3, LOG_FILE);
		// change user group (silently)
		@chgrp(LOG_FILE, FST_USER_GROUP);
	}
	
	function logUrl() {
		if ($this->is_cron) return;		// no url for cron jobs
		error_log("\n" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . ":\n", 3, LOG_FILE);
	}
	
	function clearTimer() {
		$this->time_start = 0;
	}

	function startTimer() {
		$this->time_elapsed = 0;
		$this->time_start = microtime_float();
	}
	
	function endTimer() {
		$timestart = $this->time_start;
		if ($timestart) {
			$time = microtime_float() - $timestart;
			error_log("\t\t completed in $time sec\n", 3, LOG_FILE);
			$this->time_elapsed = $time;
			$this->clearTimer();
		}
	}

	/* abstraction layer on top of Cache_Lite */
	
	function load($key) {
		$cl = new Cache_Lite(array('cacheDir' => $this->cache_dir, 'lifeTime' => FST_LIFETIME));
		return unserialize($cl->get($key, FST_GROUPNAME));
	}
	
	function save($data, $key, $info='undefined', $description='') {
		$cl = new Cache_Lite(array('cacheDir' => $this->cache_dir, 'lifeTime' => FST_LIFETIME));
		$cl->save(serialize($data), $key, FST_GROUPNAME);
		// echo '<p />'.$cl->_file.'<p />';
		$this->log($key, $info, $description);
		// change user group (silently)
		@chgrp($cl->_file, FST_USER_GROUP);

		$this->endTimer();
		return $data;
	}
	
	/* helper functions */

	// return trimmed $str
	function trim($str, $length, $trimtext = '...') {
		if (!$length || $length >= strlen($str)) $out = $str;
		else $out = substr($str, 0, $length) . $trimtext;
		
		$out = str_replace("\n", '<br />', $out);
		
		return $out;
	}
}

function microtime_float()
{
   list($usec, $sec) = explode(" ", microtime());
   return ((float)$usec + (float)$sec);
}

function today() {
	return date("m/d/y H:i");
}

function isTagInString($matchtag, $str) {
	// if match found, return true
	return (strpos($str, $matchtag.' ') !== false);
}

function checkTag($tag, $allow_tags = FST_ALLOW_TAGS) {
	// if FST_ALLOW_TAGS is not defined
	if ($allow_tags == '') return $tag;
	else {
		if (isTagInString($tag, $allow_tags)) return $tag;
		else return FST_DEFAULT_TAG;
	}
}

?>